package paper.db;

import java.sql.*;

import java.io.PrintWriter;

import java.util.regex.*;
import java.util.HashSet;
import java.util.Random;
import java.util.Iterator;
import java.util.Vector;

import paper.db.DB;
import paper.db.PaperCache;

public class Paper implements Comparable {
    final static Random r = new Random();

    public String key;
    public String title, flatTitle;
    public String authors, email;
    public HashSet<String> canonAuthors = new HashSet<String>();

    PaperCache cache;
    public int mark;           // Used to mark Papers when iterating graph algorithms

    String abstractX;
    String address ;
    String booktitle;
    String editor;
    String howpublished;
    String institution;
    String isbn;
    String issn;
    String issue;
    String journal;
    String month;
    String number;
    String pages;
    String publisher;
    String school;
    String volume;
    String year;

    public String bibTexKey;
    public String pdfFile, url;
    public int index; // for adjacency matrix
    public boolean interesting = false; // ditto

    // For versioning:
    String date, reason;
    int revision;

    public HashSet<Paper> references = new HashSet<Paper>();
    public HashSet<Paper> citations = new HashSet<Paper>();
    public HashSet<Paper> cocites = new HashSet<Paper>();

    public Paper(PaperCache pc) 
    {
        cache = pc;
        mark = 0;
        resetKeyAndRevision();
    }

    public Paper(String k, String a, String e, String t, String abstractX, String address, String booktitle, String editor, String howpublished, String institution, String isbn, String issn, String issue, String journal, String month, String number, String pages, String publisher, String school, String volume, String year, String u, PaperCache pc) {
        bibTexKey = k;
        authors = a;
        email = e;
        title = t;

        this.abstractX = abstractX;
        this.address = address ;
        this.booktitle = booktitle;
        this.editor = editor;
        this.howpublished = howpublished;
        this.institution = institution;
        this.isbn = isbn;
        this.issn = issn;
        this.issue = issue;
        this.journal = journal;
        this.month = month;
        this.number = number;
        this.pages = pages;
        this.publisher = publisher;
        this.school = school;
        this.volume = volume;
        this.year = year;
        
        url = u;
        pdfFile = "";
        flatTitle = cleanUp(t);
        resetKeyAndRevision();
        cache = pc;
    }

    public int compareTo(Object p) {
        Paper x = (Paper) p;
        int diff = ((x.references.size() + x.citations.size()) -
                    (references.size() + citations.size()));
        return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
    }

    public void resetKeyAndRevision() {
        revision = 0;
        key = Long.toHexString(r.nextLong());
        while (key.length() < 16) {
            key = "0" + key;
        }
    }

    // Dealings with persistent data.
    
    static public HashSet<Paper> loadAll(PaperCache pc) {
        return loadInternal("", pc);
    }

    public static void main(String[] args)
    {
        PaperCache pc = new PaperCache();
        HashSet<Paper> x = Paper.loadAll(pc);
        for(Iterator<Paper> i = x.iterator(); i.hasNext();)
        {
            Paper p = i.next();
            p.getCocitations();
            System.out.println(p);
        }
    }

    static public HashSet<Paper> loadOnFlatTitle(String ft, PaperCache pc) {
        return loadInternal(", flattitles f where f.flattitle = '" + DB.q(ft,256) + "' and f.paperKey = p.key", pc);
    }

    static public Paper loadOnKey(String key, PaperCache pc) {
        HashSet<Paper> hs = loadInternal(" WHERE p.key = '" + DB.q(key,16) + "'", pc);
        Iterator<Paper> i = hs.iterator();
        if (!i.hasNext()) {
            return null;
        }
        return i.next(); // cannot be more than one. Famous last words.
    }

    static public Paper loadOnBibtexKey(String key, PaperCache pc) {
        HashSet<Paper> hs = loadInternal(" WHERE p.bibtexkey = '" + DB.q(key,64) + "'", pc);
        Iterator<Paper> i = hs.iterator();
        if (!i.hasNext()) {
            return null;
        }
        return i.next(); // cannot be more than one. Famous last words.
    }

    /* Nicer semantics but slow as shit...
       Folded into loadUrlSet()
    public void mapAuthors()
    {
        Statement st = null;
        ResultSet rs = null;
        try {
            st = DB.connection.createStatement();
            rs = st.executeQuery("SELECT distinct a.authors from authors a where a.paperkey='"+key+"';");
            while(rs.next())
                canonAuthors.add(rs.getString("authors"));
        } catch (SQLException e) {
            System.err.println("Failed to map authors " + authors + ": " + e);
        } finally {
            try {
                if (rs != null) rs.close();
                if (st != null) st.close();
            } catch(Exception e) {
                System.err.println("Fuck..." + e);
            }
        }
    }*/

    public static HashSet<Paper> loadUrlSet(String where, PaperCache pc) {
        Statement st = null;
        ResultSet rs = null;
        HashSet<Paper> result = new HashSet<Paper>();
        try {
            st = DB.connection.createStatement();
            rs = st.executeQuery("SELECT p.* from userpapers u, pdffiles f, papers p where  u.md5=f.md5 and p.key=f.paperkey and personalnotes like '%" + where + "%';"); 
            while(rs.next()) {
                Paper p = null;
                if( pc != null )
                    p = pc.findKey( rs.getString(DB.key) );
                if( p != null )
                    result.add(p);
                else
                {
                    p = new Paper(pc);
                    result.add(p);
                    p.key       = rs.getString(DB.key);
                    p.revision  = rs.getInt(DB.revision);
                    p.date      = rs.getString(DB.date);
                    p.reason    = rs.getString(DB.reason);
                    
                    p.authors   = rs.getString(DB.authors);
                    p.email     = rs.getString(DB.email);
                    p.title     = rs.getString(DB.title);
                    p.bibTexKey = rs.getString(DB.bibTexKey);
                    p.pdfFile   = rs.getString(DB.pdf);
                    p.url       = rs.getString(DB.url);
                    p.abstractX = rs.getString(DB.abstractX);
                    p.address   = rs.getString(DB.address);
                    p.booktitle = rs.getString(DB.booktitle);
                    p.editor    = rs.getString(DB.editor);
                    p.howpublished = rs.getString(DB.howpublished);
                    p.institution = rs.getString(DB.institution);
                    p.isbn      = rs.getString(DB.isbn);
                    p.issn      = rs.getString(DB.issn);
                    p.issue     = rs.getString(DB.issue);
                    p.journal   = rs.getString(DB.journal);
                    p.month     = rs.getString(DB.month);
                    p.number    = rs.getString(DB.number);
                    p.pages     = rs.getString(DB.pages);
                    p.publisher = rs.getString(DB.publisher);
                    p.school    = rs.getString(DB.school);
                    p.volume    = rs.getString(DB.volume);
                    p.year      = rs.getString(DB.year);
                    
                    p.flatTitle = cleanUp(p.title);
                    if (pc != null) {
                        pc.addPaper(p);             // DUP-licatation, mmmmm, nutty
                    }
                }
                BibTex.register(p);
            }
            st = DB.connection.createStatement();
            rs = st.executeQuery("select distinct a.authors, a.paperkey from userpapers u, pdffiles f, papers p, authors a where  u.md5=f.md5 and p.key=f.paperkey and p.key=a.paperkey and personalnotes like '%" + where + "%';"); 
            while(rs.next()) {
                Paper p = pc.findKey( rs.getString("paperkey") );
                String canA = rs.getString("authors");
                int surname = canA.indexOf(' ');
                if( surname==-1 )
                    surname = canA.length();
                p.canonAuthors.add( canA.substring(0,surname) );
            }
        } catch (SQLException e) {
            System.err.println("Failed to load paper entry " + where + " " + e);
            return result;
        } finally {
            try {
                if (rs != null) rs.close();
                if (st != null) st.close();
            } catch(Exception e) {
                System.err.println("Fuck..." + e);
            }
        }
        return result;
    }

    private static HashSet<Paper> loadInternal(String where, PaperCache pc) {
        Statement st = null;
        ResultSet rs = null;
        HashSet<Paper> result = new HashSet<Paper>();
        try {
            st = DB.connection.createStatement();
            rs = st.executeQuery("SELECT p.* FROM papers p " + where);
            while(rs.next()) {
                Paper p = null;
                if( pc != null )
                    p = pc.findKey( rs.getString(DB.key) );
                if( p != null )
                    result.add(p);
                else
                {
                    p = new Paper(pc);
                    result.add(p);
                    p.key       = rs.getString(DB.key);
                    p.revision  = rs.getInt(DB.revision);
                    p.date      = rs.getString(DB.date);
                    p.reason    = rs.getString(DB.reason);
                    
                    p.authors   = rs.getString(DB.authors);
                    p.email     = rs.getString(DB.email);
                    p.title     = rs.getString(DB.title);
                    p.bibTexKey = rs.getString(DB.bibTexKey);
                    p.pdfFile   = rs.getString(DB.pdf);
                    p.url       = rs.getString(DB.url);
                    p.abstractX = rs.getString(DB.abstractX);
                    p.address   = rs.getString(DB.address);
                    p.booktitle = rs.getString(DB.booktitle);
                    p.editor    = rs.getString(DB.editor);
                    p.howpublished = rs.getString(DB.howpublished);
                    p.institution = rs.getString(DB.institution);
                    p.isbn      = rs.getString(DB.isbn);
                    p.issn      = rs.getString(DB.issn);
                    p.issue     = rs.getString(DB.issue);
                    p.journal   = rs.getString(DB.journal);
                    p.month     = rs.getString(DB.month);
                    p.number    = rs.getString(DB.number);
                    p.pages     = rs.getString(DB.pages);
                    p.publisher = rs.getString(DB.publisher);
                    p.school    = rs.getString(DB.school);
                    p.volume    = rs.getString(DB.volume);
                    p.year      = rs.getString(DB.year);
                    
                    p.flatTitle = cleanUp(p.title);
                    if (pc != null) {
                        pc.addPaper(p);             // DUP-licatation, mmmmm, nutty
                    }
                }
                BibTex.register(p);
            }
        } catch (SQLException e) {
            System.err.println("Failed to load paper entry " + where + " " + e);
            return result;
        } finally {
            try {
                if (rs != null) rs.close();
                if (st != null) st.close();
            } catch(Exception e) {
                System.err.println("Fuck..." + e);
            }
        }
        return result;
    }

    static private HashSet<String> getReferences(String key, boolean to) {
        Statement st = null;
        ResultSet rs = null;
        HashSet<String> answer = new HashSet<String>();
        String columnSelect = to ? "paper" : "cites";
        String columnWhere = !to ? "paper" : "cites";
        try {
            st = DB.connection.createStatement();
            String query ="SELECT " + columnSelect + " FROM citations "+
                                 " WHERE " + columnWhere  + "='" + DB.q(key,16) + "'";
            rs = st.executeQuery(query);
            //System.out.println("SQL: " + query);
            while(rs.next()) {
                answer.add(rs.getString(1));
            }
        } catch (SQLException e) {
            System.err.println("Failed to obtain references to " + key + " " + e);
        } finally {
            try {
                if (rs != null) rs.close();
                if (st != null) st.close();
            } catch(Exception e) {
                System.err.println("Fuck..." + e);
            }
        }
        return answer;
    }

    static public HashSet<String> getKeys() {
        Statement st = null;
        ResultSet rs = null;
        HashSet<String> answer = new HashSet<String>();
        try {
            st = DB.connection.createStatement();
            rs = st.executeQuery("SELECT key FROM papers");
            while(rs.next()) {
                answer.add(rs.getString(1));
            }
        } catch (SQLException e) {
            System.err.println("Failed to obtain all keys " + e);
        } finally {
            try {
                if (rs != null) rs.close();
                if (st != null) st.close();
            } catch(Exception e) {
                System.err.println("Fuck..." + e);
            }
        }
        return answer;
    }

    public void getReferencesTo() {
        if( references.size() == 0)
        {
            HashSet<String> keys = getReferences(key, true);
            //System.out.println("keys=" + keys.toString() );
            for(Iterator<String> i = keys.iterator(); i.hasNext();)
                references.add( loadOnKey( i.next(), cache ) );
        }
    }

    static public HashSet<String> getReferencesTo(String key) {
        return getReferences(key, true);
    }

    public void getCitationsFrom() {
        if( citations.size() == 0)
        {
            HashSet<String> keys = getReferences(key, false);
            for(Iterator<String> i = keys.iterator(); i.hasNext();)
                citations.add( loadOnKey( i.next(), cache ) );
        }
    }

    static public HashSet<String> getCitationsFrom(String key) {
        return getReferences(key, false);
    }

    public void getCocitations() {
        getReferencesTo(); 
        for(Iterator<Paper> i = references.iterator(); i.hasNext();)
        {
            Paper ref = i.next();
            //System.out.println("REF: " + ref);
            ref.getCitationsFrom();
            for(Iterator<Paper> j = ref.citations.iterator(); j.hasNext();)
            {
                Paper cocite = j.next();
                if( cocite != this) 
                {
                    //System.out.println("COCITE: " + cocite.toString());
                    cocites.add( cocite);
                }
            }
        }
    }

    static public HashSet<Paper> toPaper(HashSet<String> in, PaperCache pc) {
        HashSet<Paper> out = new HashSet<Paper>();
        for(Iterator<String> i = in.iterator(); i.hasNext();) {
            String k = i.next();
            Paper p = BibTex.findKey(k);
            if (p == null) {
                Paper.loadOnKey(k, pc);
            }
            out.add(p);
        }
        return out;
    }


    public HashSet<Paper> getReferencesToMe() {
        if (references != null) {
            references = toPaper(Paper.getReferencesTo(key), cache);
        }
        return references;
    }

    public HashSet<Paper> getCitations() {
        if (citations != null) {
            citations = toPaper(Paper.getCitationsFrom(key), cache);
        }
        return citations;
    }

    public void addCitation(Paper to) {
        addCitation(to, 0f, 0, "", "", "");
    }

    public void addCitation(Paper to, double prob, int outOf,
                            String citedAuthors, String citedTitle,
                            String citedDetails) {
        citations.add(to);
        to.references.add(this);
        interesting = true;
        to.interesting = true;
        Paper.commitReference(key, to.key, prob, outOf, citedAuthors, citedTitle, citedDetails);
    }

    /*
     * Add a reference to the database; only do so if that links isn't in
     * the database already.
     */
    static public void commitReference(String from, String to,
                                       double prob, int outOf,
                                       String citedAuthors, String citedTitle,
                                       String citedDetails) {
        try {
            Statement st = DB.connection.createStatement();
            ResultSet rs = st.executeQuery("SELECT paper FROM citations WHERE"+
                                           " paper='" + DB.q(from,16) + "' and" +
                                           " cites='" + DB.q(to,16)   + "'");
            if (!rs.next()) {
                st.executeUpdate("INSERT INTO citations (paper,cites,probability,outof,authors,title,howpublished) VALUES (" +
                                 "'"+ DB.q(from,16) +"'," +
                                 "'"+ DB.q(to,16) +"'," +
                                 "'"+ prob +"'," +
                                 "'"+ outOf +"'," +
                                 "'"+ DB.q(sanitise(citedAuthors), DB.authorsMax) +"'," +
                                 "'"+ DB.q(sanitise(citedTitle), DB.titleMax) +"'," +
                                 "'"+ DB.q(sanitise(citedDetails), DB.howpublishedMax) +"')");
            }
            st.close();
        } catch (SQLException e) {
            System.err.println("Failed to insert ref (" + from + " -> " + to + ")" + " " + e);
        }
    }

    /*
     * Add a reference to the database; only do so if that links isn't in
     * the database already.
     */
    public void deleteReferences() {
        try {
            Statement st = DB.connection.createStatement();
            st.executeUpdate("DELETE FROM citations WHERE"+
                             " paper='" + key + "'");
            st.close();
        } catch (SQLException e) {
            System.err.println("Failed to delete refs " + key + " " + e);
        }
    }

    private static Pattern longWord = Pattern.compile("[^ \n\r]{33}");
    public static String sanitise(String x) {
        Matcher m = longWord.matcher(x);
        while (m.find()) {
            x = x.substring(0,m.end()-1) + " " + x.substring(m.end()-1, x.length());
            m = longWord.matcher(x);
        }
        return x;
    }

    public void commitEntry(String reason) {
        String today = DB.today();
        Statement st = null;
        String query = null;
        try {
            st = DB.connection.createStatement();
            if (revision != 0) {
                st.executeUpdate("UPDATE papers SET key='" + DB.q(key,16) + "-" + revision + "' WHERE key='"+ DB.q(key,16) + "'");
            } else {
                query = ("INSERT INTO flattitles (paperKey,flattitle) " +
                         "VALUES ('"+ DB.q(key,16) +"'," +
                         "'"+ DB.q(flatTitle,DB.flattitleMax) +"')");
                st.executeUpdate(query);
            }
            revision++;
            date = today;
            authors = sanitise(authors);
            title = sanitise(title);
            query = ("INSERT INTO papers " +
                     "(key,authors,email,title,abstract,address,booktitle,editor,howpublished,institution,isbn,issn,issue,journal,month,number,pages,publisher,school,volume,year,url,pdf,bibtexkey,date,reason,revision) " +
                     "VALUES ('"+ DB.q(key,16) +"'," +
                     "'"+ DB.q(authors,DB.authorsMax) +"'," + 
                     "'"+ DB.q(email,DB.emailMax) +"'," + 
                     "'"+ DB.q(title,DB.titleMax) +"'," + 
                     "'"+ DB.q(abstractX,DB.abstractXMax) +"'," +
                     "'"+ DB.q(address,DB.addressMax) +"'," +
                     "'"+ DB.q(booktitle,DB.booktitleMax) +"'," +
                     "'"+ DB.q(editor,DB.editorMax) +"'," +
                     "'"+ DB.q(howpublished,DB.howpublishedMax) +"'," +
                     "'"+ DB.q(institution,DB.institutionMax) +"'," +
                     "'"+ DB.q(isbn,DB.isbnMax) +"'," +
                     "'"+ DB.q(issn,DB.issnMax) +"'," +
                     "'"+ DB.q(issue,DB.issueMax) +"'," +
                     "'"+ DB.q(journal,DB.journalMax) +"'," +
                     "'"+ DB.q(month,DB.monthMax) +"'," +
                     "'"+ DB.q(number,DB.numberMax) +"'," +
                     "'"+ DB.q(pages,DB.pagesMax) +"'," +
                     "'"+ DB.q(publisher,DB.publisherMax) +"'," +
                     "'"+ DB.q(school,DB.schoolMax) +"'," +
                     "'"+ DB.q(volume,DB.volumeMax) +"'," +
                     "'"+ DB.q(year,DB.yearMax) +"'," +
                     "'"+ DB.q(url,DB.urlMax) +"'," +
                     "'"+ DB.q(pdfFile,DB.pdfMax) +"'," +
                     "'"+ DB.q(bibTexKey,DB.bibTexKeyMax) +"'," + 
                     "'"+ date +"'," + 
                     "'"+ DB.q(reason,DB.reasonMax) +"'," + 
                     "'"+ revision +"')");
            st.executeUpdate(query);
        } catch (SQLException e) {
            System.err.println("Failed to update " + key + ": " + e + " ---" + query + "---");
        } finally {
            try {
                if (st != null) st.close();
            } catch(Exception e) {
                System.err.println("Fuck..." + e);
            }
        }
    }

    // Local functions

    public static String cleanUp(String x) {
        StringBuilder sb = new StringBuilder();
        char oc = ' ';
        for (int i = 0; i < x.length(); i++) {
            char c = x.charAt(i);
            if (Character.isDigit(c)) {
                if (oc == 'a') {                   // delete latex a0 a1 a2...
                    sb.deleteCharAt(sb.length()-1);
                    while (i+1 < x.length() &&
                           Character.isDigit(x.charAt(i+1))) {
                        i++;
                    }
                    oc = ' ';
                    continue;
                }
                sb.append(c);
            } else {
                if (Character.isUpperCase(c)) {
                    c = Character.toLowerCase(c);
                }
                if (Character.isLowerCase(c)) {
                    if (oc == 'f') {
                        if (c == 'i') {
                            sb.deleteCharAt(sb.length()-1);//  remove fi
                            oc = ' ';
                            continue;
                        } else if (c == 'l') {
                            sb.deleteCharAt(sb.length()-1);//  remove fl
                            oc = ' ';
                            continue;
                        } else if (c == 'f') {
                            sb.deleteCharAt(sb.length()-1);//  remove ff
                            if (i != x.length() -1 &&
                                x.charAt(i+1) == 'i') {
                                i++;                       //  remove i of ffi
                            }
                            oc = ' ';
                            continue;
                        }
                    }
                    sb.append(c);
                }
            }
            oc = c;
        }
        return sb.toString();
    }

    public void mergeInformation(String betterAuthor, String betterEmail,
                                 String betterTitle, String betterYear) {
        String reason = "";
        if (title.indexOf("#") != -1 && betterTitle.indexOf("#") == -1) {
            title = betterTitle;
            reason += "+Title#";
        } else if (betterTitle.length() > title.length()) {
            title = betterTitle;
            reason += "+Title";
        }
        if (year.equals("") && !betterYear.equals("")) {
            year = betterYear;
            reason += "+Year";
        }
        if (email.equals("") && !betterEmail.equals("")) {
            email = betterEmail;
            reason += "+Email";
        }
        boolean dbHasHash = authors.indexOf("#") != -1;
        boolean betHasHash = betterAuthor.indexOf("#") != -1;
        if (dbHasHash && !betHasHash) {
            authors = betterAuthor;
            reason += "+Author#";
        }
        if (!reason.equals("")) {
            commitEntry(reason);
        }
    }
    
    public static Vector<String> getAuthorsCiting(String key) {
        try {
            Vector<String> result = new Vector<String>();
            Statement st = DB.connection.createStatement();
            ResultSet rs = st.executeQuery("SELECT authors FROM citations "+
                                           " WHERE cites='" + key + "'");
            while(rs.next()) {
                result.add(rs.getString(1));
            }
            st.close();
            return result;
        } catch (SQLException e) {
            System.err.println("Failed to get citedAuthors " + key + " " + e);
        }
        return null;
    }

    public static Vector<String> getAuthorsPDFFile(String key) {
        try {
            Vector<String> result = new Vector<String>();
            Statement st = DB.connection.createStatement();
            ResultSet rs = st.executeQuery("SELECT authors FROM pdffiles "+
                                           " WHERE paperkey='" + key + "'");
            while(rs.next()) {
                result.add(rs.getString(1));
            }
            st.close();
            return result;
        } catch (SQLException e) {
            System.err.println("Failed to get pdffileauths " + key + " " + e);
        }
        return null;
    }

    public static void setSaneAuthors(String key, Vector<String> sane) {
        try {
            Statement st = DB.connection.createStatement();
            st.executeUpdate("DELETE FROM authors "+
                             " WHERE paperkey='"+key+"'");
            StringBuilder sb = new StringBuilder();
            int cnt = 0;
            for (Iterator<String> i = sane.iterator(); i.hasNext(); ) {
                String a = i.next();
                if (sb.length() != 0) {
                    sb.append("; ");
                }
                sb.append(a);
                st.executeUpdate("INSERT INTO authors (authors,sequence,paperkey) VALUES"+
                                 "('" + DB.q(a,DB.authorsMax) + "'," +
                                 "" + (++cnt) + "," +
                                 "'"+key+"')");
            }
            st.executeUpdate("UPDATE papers SET authors="+
                             "'" + DB.q(sb.toString(),DB.authorsMax) + "'" +
                             " WHERE key='"+key+"'");
            st.close();
        } catch (SQLException e) {
            System.err.println("Failed to set sane Authors " + key + " " + e);
        }
    }


    // I/O functions

    public void makeBibTexFile() {
        PrintWriter pw = null;
        try {
            pw = new PrintWriter("html/bibtex-" + key + ".html", "ISO-8859-1" );
        } catch (Exception e) {
            System.err.print("Huh?" + e);//Unsupported encoding
        }
        pw.println("<html><title>" + title + "</title><body>");

        pw.println("Title: <em>" + title.replaceAll("\n","<!--\\n--> ") + "</em><br />");
        pw.println("Author: <b>" + authors + "</b><br />");
        pw.println("URL: " + url + "<br />");
        pw.println("<a href='data-" + key + ".html'>Data file</a>.<br />" );
        if (citations.size() != 0) {
            for(Iterator<Paper> i = citations.iterator(); i.hasNext();){
                String toKey = i.next().key;
                pw.println("Cites: ");
                pw.println("<tt><a href='bibtex-" + toKey + ".html'>" + toKey + "</a></tt><br />");
            }
        }
        if (references.size() != 0) {
            for(Iterator<Paper> i = references.iterator(); i.hasNext();){
                String fromKey = i.next().key;
                pw.println("Referenced from: ");
                pw.println("<tt><a href='bibtex-" + fromKey + ".html'>" + fromKey + "</a></tt><br />");
            }
        }
        pw.println("</body></html>");
        pw.close();
    }

    public String toString()
    {
        return cocites.size()+ " : " + title.replace('\n',' ') + " : " + key ;
    }

}
